package db

import (
	"fmt"
	"functime"
	"sync"

	mgo "gopkg.in/mgo.v2"
	bson "gopkg.in/mgo.v2/bson"
)

var globalSession *mgo.Session

func Mongo_InitData() {
	InitDB()
	ensureUsersIndexes()
	for i := 0; i < PlayerNum; i++ {
		uid := 1000 + i + 1
		user := NewUser(uid)
		user.Save()
	}
}

func Mongo_RunStress() {
	fc := functime.NewFuncTime()
	defer fc.End("Mongo_RunStress")

	wg := sync.WaitGroup{}
	for n := 0; n < PlayerNum; n++ {
		wg.Add(1)
		func() {
			uids := []int{}
			for i := 1; i <= FriendNum; i++ {
				uid := 1000 + i
				uids = append(uids, uid)
			}
			loadUsersFromDB(uids)
			wg.Done()
		}()
	}

	wg.Wait()
}

//////////////////////////////////////////////////////////////////

func InitDB() {
	session, err := mgo.Dial("mongodb://127.0.0.1/test2")
	if err != nil {
		panic(err)
	}
	session.SetMode(mgo.Strong, true)
	globalSession = session
}

func (u *User) Save() error {
	const usersColl = "users"
	return CollScoped(usersColl, func(c *mgo.Collection) error {
		//BUG: user with same id will overwrite previous user in DB
		// maybe we can generate ObjectId ourselves
		if _, err := c.Upsert(bson.M{"uid": u.UID}, *u); err != nil {
			fmt.Println("error saving user:", err)
			return err
		}
		return nil
	})
}

func loadUsersFromDB(uids []int) ([]*User, error) {
	s, c := DBUsers()
	defer s.Close()

	whereUids := []bson.M{}
	for _, uid := range uids {
		bm := bson.M{"uid": uid}
		whereUids = append(whereUids, bm)
	}

	where := bson.M{"$or": whereUids}
	users := []*User{}
	if err := c.Find(where).All(&users); err != nil {
		if err == mgo.ErrNotFound {
			return nil, nil
		}
		return nil, err
	}

	//	spew.Dump(users)

	return users, nil
}

func DBUsers() (*mgo.Session, *mgo.Collection) {
	const usersColl = "users"
	session := Session()
	return session, session.DB("").C(usersColl)
}

func Session() *mgo.Session {
	if globalSession == nil {
		InitDB()
	}
	return globalSession.Copy()
}

func Scope(f func(*mgo.Session)) {
	s := Session()
	defer s.Close()
	f(s)
}

func CollScoped(coll string, f func(*mgo.Collection) error) error {
	s := Session()
	defer s.Close()
	c := s.DB("").C(coll)
	return f(c)
}

func ensureUsersIndexes() {
	s, c := DBUsers()
	defer s.Close()

	c.EnsureIndex(mgo.Index{
		Key:    []string{"uid"},
		Unique: true,
	})
}
